package com.cxz.spring.v2.servlet;

import com.cxz.spring.v2.annotation.CxzAutowired;
import com.cxz.spring.v2.annotation.CxzController;
import com.cxz.spring.v2.annotation.CxzRequestMapping;
import com.cxz.spring.v2.annotation.CxzService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author cxz
 * @version V1.0
 * @Date 2022/9/7 17:17
 * @desc
 */
public class CxzDispatcherServlet extends HttpServlet {
    private Map<String, Object> map = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            resp.setContentType("text/html;charset=utf-8");
            this.myDoDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("发生了500异常，异常原因：" + e.getMessage());
        }
    }

    /**
     * 转发处理器
     * @param req
     * @param resp
     * @throws Exception
     */
    private void myDoDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 获取url
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();

        // 获取相对路径
        String url = uri.replace(contextPath, "").replace("/+", "/");
        url = Objects.equals("", url) ? "/" : url;

        if(!this.map.containsKey(url)) {
            resp.getWriter().write("请求资源不存在，抛404");
        }

        // 将存放到map中的对象，还原为method类型
        Method method = (Method)this.map.get(url);

        // 获取请求参数
        Map<String, String[]> params = req.getParameterMap();

        // 调用匹配到的method方法
        Object o = this.map.get(method.getDeclaringClass().getName());

        if(params.containsKey("name")) {
            String name = params.get("name")[0];
            method.invoke(o, new Object[]{req, resp, name});
        } else {
            method.invoke(o, new Object[]{req, resp});
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        InputStream configStream = null;
        try {
            // 根据web.xml配置，加载application.properties配置文件
            String contextConfigLocation = config.getInitParameter("contextConfigLocation");
            configStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

            Properties configContext = new Properties();
            configContext.load(configStream);
            // 读取扫描包配置
            String scanPackageName = configContext.getProperty("scanPackage");
            // 处理包路径，将其添加到map中
            handleScanPackage(scanPackageName);

            // 遍历map的key
            List<String> keyList = new ArrayList<>(map.keySet());
            for(String clazzName : keyList) {
                if(!clazzName.contains(".")) {
                    continue;
                }

                // 加载类，如果是controller
                Class<?> clazz = Class.forName(clazzName);
                if(clazz.isAnnotationPresent(CxzController.class)) {
                    map.put(clazzName, clazz.newInstance());
                    // 如果类上面添加了@requestMapping注解，则外面映射的路径需要添加基本url
                    String baseURL = "";
                    if(clazz.isAnnotationPresent(CxzRequestMapping.class)) {
                        baseURL = clazz.getAnnotation(CxzRequestMapping.class).value();
                    }

                    // 获取controller中所有方法
                    Method[] methods = clazz.getMethods();
                    for(Method method : methods) {
                        if(!method.isAnnotationPresent(CxzRequestMapping.class)) {
                            continue;
                        }
                        CxzRequestMapping requestMapping = method.getAnnotation(CxzRequestMapping.class);
                        String url = (baseURL + "/" + requestMapping.value()).replaceAll("/+", "/");
                        map.put(url, method);
                    }
                }
                // 如果类的注解是，@Service
                if(clazz.isAnnotationPresent(CxzService.class)) {
                    CxzService service = clazz.getAnnotation(CxzService.class);
                    String beanName = service.value();

                    // 如果@Service注解没有指定bean名称，则使用类名作为bean名称
                    if(Objects.equals("", beanName)) {
                        beanName = clazz.getName();
                    }
                    // 初始化bean，并且将bean添加到map中
                    Object instance = clazz.newInstance();
                    map.put(beanName, instance);
                    for(Class<?> i : clazz.getInterfaces()) {
                        map.put(i.getName(), instance);
                    }
                }
            }

            // 遍历map的值
            for(Object obj : map.values()) {
                if(Objects.isNull(obj)) {
                    continue;
                }

                // 如果是controller类型，则需要
                Class<?> clazz = obj.getClass();
                if(clazz.isAnnotationPresent(CxzController.class)) {
                    Field[] fields = clazz.getDeclaredFields();
                    for(Field field : fields) {
                        if(!field.isAnnotationPresent(CxzAutowired.class)) {
                            continue;
                        }
                        CxzAutowired autowired = field.getAnnotation(CxzAutowired.class);
                        String beanName = autowired.value();
                        if(Objects.equals(beanName, "")) {
                            beanName = field.getType().getName();
                        }
                        field.setAccessible(true);
                        try {
                            field.set(map.get(clazz.getName()), map.get(beanName));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(Objects.nonNull(configStream)) {
                try {
                    configStream.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 处理包扫描问题，将包内所有class文件，转为类名，添加的全局map中，其中key就是类名
     * @param scanPackageName
     */
    private void handleScanPackage(String scanPackageName) {
        // 根据包名，获取到扫描目录路径
        String scanPackagePath = "/" + scanPackageName.replaceAll("\\.", "/");
        URL scanPackageUrl = this.getClass().getClassLoader().getResource(scanPackagePath);
        File scanPackageClassDir = new File(scanPackageUrl.getFile());

        // 遍历目录中，所有文件
        for(File classFile : scanPackageClassDir.listFiles()) {
            // 如果文件是目录，则递归处理扫描
            if(classFile.isDirectory()) {
                handleScanPackage(scanPackageName + "." + classFile.getName());
            } else {
                // 如果文件不是class文件，则跳过
                if(!classFile.getName().endsWith(".class")) {
                    continue;
                }
                // 否则根据class文件名，以及扫描包名称，获取需要加载的类名
                String clazzName = scanPackageName + "." + classFile.getName().replace(".class", "");
                map.put(clazzName, null);
            }
        }
    }
}
